<script setup>
import { useElementBounding, useElementSize, useEventListener } from '@vueuse/core'

const props = defineProps({
  modelValue: {
    type: Number,
    default: (v) => {
      if (!v.modelValue) return 0.5
      return v > 1 ? 1 : v
    },
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  hiddenFirst: Boolean,
  hiddenLast: Boolean,
  horizontal: {
    type: Boolean,
    default: false,
  },
  initMax: {
    type: Boolean,
    default: true,
  },
  // 主要容器 left, top, right, bottom
  mainContainer: {
    type: String,
    default: (p) => {
      if ((!p.leftMaxWidth && p.rightMaxWidth) || (!p.topMaxHeight && p.bottomMaxHeight)) {
        return p.horizontal ? 'bottom' : 'right'
      }
      return p.horizontal ? 'top' : 'left'
    },
  },
  leftMinWidth: {
    type: Number,
    default: 0,
  },
  leftMaxWidth: Number,
  rightMinWidth: {
    type: Number,
    default: 0,
  },
  rightMaxWidth: Number,
  topMinHeight: {
    type: Number,
    default: 0,
  },
  topMaxHeight: Number,
  bottomMinHeight: {
    type: Number,
    default: 0,
  },
  bottomMaxHeight: Number,
})

const emit = defineEmits(['move', 'moveStart', 'moveFinish', 'change', 'boundary', 'update:modelValue'])

// 是否触发移动
const isMove = ref(false)
/**
 * 是否锁定容器
 * @type {boolean}
 */
let containerLock = false
/**
 * 锁定状态名称
 * @type {import('vue').Ref<string>}
 */
let containerLockName = ref('')
// 容器锁定状态
const containerLockState = reactive({
  left: false,
  right: false,
  top: false,
  bottom: false,
})

// 滑块
const sliderRef = ref(null)
const sliderSize = useElementSize(sliderRef, {
  width: 12,
  height: 12,
})

// 根节点
const rootRef = ref(null)
const rootBound = useElementBounding(rootRef)

watch(
  () => (props.horizontal ? rootBound.height.value : rootBound.width.value),
  (newVal, oldVal) => {
    if (oldVal) {
      if (props.hiddenFirst) {
        container1Per.value = 0
      } else if (props.hiddenLast) {
        container1Per.value = 1
      } else {
        const oldContainer1Size = container1Per.value * oldVal
        const per = parseFloat((oldContainer1Size / newVal).toFixed(6))
        container1Per.value = per > 1 ? 1 : per
      }
      emitChange()
    }
  }
)

// 第一个容器是否是主容器
const firstContainerIsMain = computed(() => {
  return ['left', 'top'].includes(props.mainContainer)
})

// 容器大小
const containerPer = ref(props.modelValue)

watch(
  () => props.modelValue,
  (v) => {
    containerPer.value = v
  }
)

const container1Per = computed({
  get() {
    return containerPer.value
  },
  set(v) {
    containerPer.value = v
    emit('update:modelValue', v)
  },
})

const container2Per = computed(() => {
  return 1 - container1Per.value
})
const container1Size = computed(() => {
  return `${container1Per.value * 100}%`
})
const container2Size = computed(() => {
  return `${container2Per.value * 100}%`
})

watch(
  () => props.hiddenFirst,
  (v) => {
    if (v === true) {
      container1Per.value = 0
    }
  }
)

watch(
  () => props.hiddenLast,
  (v) => {
    if (v === true) {
      container1Per.value = 1
    }
  }
)

const flexDirection = computed(() => {
  return props.horizontal ? 'column' : 'row'
})
const userSelect = computed(() => {
  return isMove.value ? 'none' : null
})

const boundaryArr = props.horizontal
  ? ['topMinHeight', 'topMaxHeight', 'bottomMinHeight', 'bottomMaxHeight']
  : ['leftMinWidth', 'leftMaxWidth', 'rightMinWidth', 'rightMaxWidth']

function handleContainerLock(per1, per2) {
  const size = props.horizontal ? rootBound.height.value : rootBound.width.value
  for (let key of boundaryArr) {
    const value = props[key]
    if (value) {
      const isMin = key.includes('Min')
      const isFirstContainer = key.includes('left') || key.includes('top')
      const per = isFirstContainer ? per1 : per2
      const isPx = value > 1
      const slider = props.horizontal ? sliderSize.height.value : sliderSize.width.value
      if (isMin) {
        if (isFirstContainer) {
          containerLock = (isPx ? size * per : per) <= value
        } else {
          containerLock = (isPx ? size * per - slider : per) <= value
        }
      } else {
        if (isFirstContainer) {
          containerLock = (isPx ? size * per : per) >= value
        } else {
          containerLock = (isPx ? size * per - slider : per) >= value
        }
      }
      if (containerLock) {
        containerLockName.value = key
        emit('boundary', key)
        return isPx ? parseFloat(((isFirstContainer ? value : value + slider) / size).toFixed(6)) : value
      } else {
        containerLockName.value = ''
      }
    }
  }
}

function handleMouseMove(ev) {
  if (isMove.value) {
    const size = props.horizontal ? rootBound.height.value : rootBound.width.value
    const offset = props.horizontal ? rootBound.top.value : rootBound.left.value
    const clientSize = props.horizontal ? ev.clientY : ev.clientX

    // 百分比
    const per = parseFloat(((clientSize - offset) / size).toFixed(6))
    const slider = props.horizontal ? sliderSize.height.value : sliderSize.width.value
    setContainer1Per(per)
    emit('move', {
      container1Per: container1Per.value,
      container2Per: container2Per.value,
      container1Size: size * container1Per.value,
      container2Size: size * container2Per.value - slider,
    })
  }
}

function setContainer1Per(per) {
  const per1 = per > 1 ? 1 : per
  const per2 = 1 - per1
  const boundaryPer = handleContainerLock(per1, per2)
  if (containerLock) {
    if (['left', 'top'].some((i) => containerLockName.value.includes(i))) {
      container1Per.value = boundaryPer
    } else {
      container1Per.value = 1 - boundaryPer
    }
  } else {
    container1Per.value = per1
  }
  emitChange()
}

function handleMouseDown() {
  isMove.value = true
  for (let key in containerLockState) {
    containerLockState[key] = false
  }
  emit('moveStart')
}

function handleMouseUp() {
  if (isMove.value) {
    isMove.value = false
    updateLockState()
    const size = props.horizontal ? rootBound.height.value : rootBound.width.value
    const slider = props.horizontal ? sliderSize.height.value : sliderSize.width.value
    emit('moveFinish', {
      container1Per: container1Per.value,
      container2Per: container2Per.value,
      container1Size: size * container1Per.value,
      container2Size: size * container2Per.value - slider,
    })
  }
}

function updateLockState() {
  const key = ['left', 'right', 'top', 'bottom'].find((i) => containerLockName.value.includes(i))
  if (containerLockName.value) {
    containerLockState[key] = true
  }
}

const autoFlag = ref(false)

/**
 * 手动伸缩
 * @param boundaryKey {null | 'leftMinWidth' | 'leftMaxWidth'| 'rightMinWidth'| 'rightMaxWidth'| 'topMinHeight'| 'topMaxHeight'| 'bottomMinHeight'| 'bottomMaxHeight'}
 */
function flexible(boundaryKey) {
  if (boundaryKey && props[boundaryKey]) {
    containerLockName.value = boundaryKey
    updateLockState()
    const size = props.horizontal ? rootBound.height.value : rootBound.width.value
    const isFirstContainer = boundaryKey.includes('left') || boundaryKey.includes('top')
    const isPx = props[boundaryKey] > 1
    const slider = isFirstContainer ? 0 : props.horizontal ? sliderSize.height.value : sliderSize.width.value
    const per = isPx ? (props[boundaryKey] + slider) / size : props[boundaryKey]
    const value = parseFloat((isFirstContainer ? per : 1 - per).toFixed(6))
    autoFlag.value = true
    container1Per.value = value
    setTimeout(() => {
      autoFlag.value = false
    }, 300)
    emitChange()
  }
}

function init() {
  const fn = (key) => {
    const value = props[key]
    const isPx = value > 1
    const isFirstContainer = key.includes('left') || key.includes('top')
    const isMin = key.includes('Min')
    const size = props.horizontal ? rootBound.height.value : rootBound.width.value
    const slider = props.horizontal ? sliderSize.height.value : sliderSize.width.value
    let p
    if (isPx) {
      p = parseFloat((isFirstContainer ? value / size : (size - value - slider) / size).toFixed(6))
    } else {
      p = isFirstContainer ? value : 1 - value
    }
    // 当以最小为准且是第一个容器时 或者 以最大值为准且不是第一个容器时, 计算比例和传入比例取最大值
    // 第一种情况(以最小为准且是第一个容器时): 当传入比例比计算比例小时, 超过了第一个容器的最小宽度, 所以以计算值为准
    // 第二种情况(以最大值为准且不是第一个容器时): 当传入比例比计算比例小时, 超过了第二个容器的最大宽度, 所以以计算值为准
    if ((isMin && isFirstContainer) || (!isMin && !isFirstContainer)) {
      container1Per.value = props.modelValue < p ? p : props.modelValue
    }
    // 当以最大为准且是第一个容器时 或者 以最小值为准且不是第一个容器时, 计算比例和传入比例取最大值
    // 第一种情况(以最大为准且是第一个容器时): 当传入比例比计算比例大时, 超过了第一个容器的最大宽度, 所以以计算值为准
    // 第二种情况(以最小值为准且不是第一个容器时): 当传入比例比计算比例大时, 超过了第二个容器的最小宽度, 所以以计算值为准
    else {
      container1Per.value = props.modelValue > p ? p : props.modelValue
    }
  }
  if (props.horizontal) {
    if (firstContainerIsMain.value) {
      if (props.topMinHeight && !props.initMax) fn('topMinHeight')
      if (props.topMaxHeight && props.initMax) fn('topMaxHeight')
    } else {
      if (props.bottomMinHeight && !props.initMax) fn('bottomMinHeight')
      if (props.bottomMaxHeight && props.initMax) fn('bottomMaxHeight')
    }
  } else {
    if (firstContainerIsMain.value) {
      if (props.leftMinWidth && !props.initMax) fn('leftMinWidth')
      if (props.leftMaxWidth && props.initMax) fn('leftMaxWidth')
    } else {
      if (props.rightMinWidth && !props.initMax) fn('rightMinWidth')
      if (props.rightMaxWidth && props.initMax) fn('rightMaxWidth')
    }
  }
}

// d1 表示水平方向,有 left,right; d2 表示垂直方向, 有 top,bottom
/**
 * 处理容器大小样式
 * @param d1 {'left' | 'right'}
 * @param d2 {'top' | 'bottom'}
 * @returns {{minHeight: (false|string), maxHeight: (false|string), flexShrink: (number|null), width: (null|string), minWidth: (false|string), height: (string|null), maxWidth: (false|string)}}
 */
function handleContainerStyle([d1, d2]) {
  const isMin = containerLockName.value.includes('Min')
  const isFirstContainer = d1 === 'left' || d2 === 'top'
  const isPx = (v) => {
    return v > 1
  }
  let width = props.horizontal ? null : isFirstContainer ? container1Size.value : container2Size.value
  let height = props.horizontal ? (isFirstContainer ? container1Size.value : container2Size.value) : null
  const minWidth = isPx(props[`${d1}MinWidth`]) ? `${props[`${d1}MinWidth`]}px` : null
  const maxWidth = isPx(props[`${d1}MaxWidth`]) ? `${props[`${d1}MaxWidth`]}px` : null
  const minHeight = isPx(props[`${d2}MinHeight`]) ? `${props[`${d2}MinHeight`]}px` : null
  const maxHeight = isPx(props[`${d2}MaxHeight`]) ? `${props[`${d2}MaxHeight`]}px` : null
  return {
    // width: props.horizontal
    //   ? null
    //   : containerLockState[d1]
    //   ? isMin
    //     ? minWidth
    //     : maxWidth
    //   : isFirstContainer
    //   ? container1Size.value
    //   : container2Size.value,
    // height: props.horizontal
    //   ? containerLockState[d2]
    //     ? isMin
    //       ? minHeight
    //       : maxHeight
    //     : isFirstContainer
    //     ? container1Size.value
    //     : container2Size.value
    //   : null,
    // minWidth: !containerLockState[d1] && minWidth,
    // maxWidth: !containerLockState[d1] && maxWidth,
    // minHeight: !containerLockState[d2] && minHeight,
    // maxHeight: !containerLockState[d2] && maxHeight,
    width,
    height,
    minWidth,
    maxWidth,
    minHeight,
    maxHeight,
    flexShrink: [d1, d2].includes(props.mainContainer) ? 0 : null,
  }
}

const container1Style = computed(() => {
  return handleContainerStyle(['left', 'top'])
})

const sliderStyle = computed(() => {
  return {
    cursor: props.disabled ? null : props.horizontal ? 'row-resize' : 'col-resize',
    width: props.horizontal ? null : `12px`,
    height: props.horizontal ? `12px` : null,
  }
})

const container2Style = computed(() => {
  return handleContainerStyle(['right', 'bottom'])
})

function setContainer1Size(value) {
  const size = props.horizontal ? rootBound.height.value : rootBound.width.value
  const per = parseFloat((value / size).toFixed(6))
  setContainer1Per(per)
}

function setContainer2Size(value) {
  const size = props.horizontal ? rootBound.height.value : rootBound.width.value
  const per = parseFloat(((size - value) / size).toFixed(6))
  setContainer1Per(per)
}

function emitChange() {
  const size = props.horizontal ? rootBound.height.value : rootBound.width.value
  const slider = props.horizontal ? sliderSize.height.value : sliderSize.width.value
  emit('change', {
    container1Per: container1Per.value,
    container2Per: container2Per.value,
    container1Size: size * container1Per.value,
    container2Size: size * container2Per.value - slider,
  })
}

onMounted(() => {
  init()
  useEventListener('mousemove', handleMouseMove)
  useEventListener('mouseup', handleMouseUp)
})

defineExpose({
  flexible,
  setContainer1Size,
  setContainer2Size,
})
</script>

<template>
  <div class="partition" ref="rootRef" :style="{ flexDirection, userSelect }">
    <div class="container-1" :class="{ transition: autoFlag }" v-show="!props.hiddenFirst" :style="container1Style">
      <slot name="left"></slot>
    </div>
    <div
      class="slider"
      v-show="!props.hiddenFirst && !props.hiddenLast"
      ref="sliderRef"
      @mousedown="!props.disabled && handleMouseDown()"
      :style="sliderStyle"></div>
    <div class="container-2" :class="{ transition: autoFlag }" v-show="!props.hiddenLast" :style="container2Style">
      <slot name="right"></slot>
    </div>
  </div>
</template>

<style scoped lang="scss">
.partition {
  display: flex;
  width: 100%;
  height: 100%;
  .slider {
    flex-shrink: 0;
  }
  .container-1,
  .container-2 {
    height: 100%;
    overflow: hidden;
    &.transition {
      transition: all 0.3s;
    }
  }
}
</style>
